package fr.emse.tatiana.remotecontrol;

import java.util.*;
import java.util.concurrent.Executor;

import org.eclipse.ui.PlatformUI;

import com.sun.tools.javac.util.Pair;

import fr.emse.tatiana.replayable.ContinuousReplayable;
import fr.emse.tatiana.replayable.DiscreteReplayable;
import fr.emse.tatiana.replayable.Replayable;
import fr.emse.tatiana.replayable.SynchronisationEvent;

public class RemoteControl extends Thread implements ContinuousReplayable {

	private static int MIN_DELTA_BETWEEN_EVENTS = 50; // gives us 20 events/second
	
	private Schedule schedule;
	private Set<DiscreteReplayable> replayerList;
	private Set<ContinuousReplayable> continuousList;
	private int speed = 1;
	private boolean paused = true;
	private boolean end = false;
	private long currentTime;
	//private RemoteWidget rw;
	private RemoteWidgetSWT rwSWT;
	private boolean timeChanged;
	public boolean ready=false;
	private GotoExecutor executor = new GotoExecutor();

	private boolean idsynch = false;

	@Override
	public void start() {
		super.start();
	}



	@Override
	public void run() {
		//rw = new RemoteWidget(this);
		ready=true;
		while (!end) { // until the object is over
			this.setCurrentTime(schedule.getFirstTime());
			schedule.next();
			while (schedule.getCurrent()!=null) {
				interrupted();
				while (paused) {
					try {
						System.err.println("REMOTE: paused and waiting");
						this.waitFor();
					} catch (InterruptedException e) {
						if (this.timeChanged) {
							schedule.goTo(this.currentTime);
							this.timeChanged=false;
						} // otherwise go test for paused
						if (end) return;
					}
				}
				long waittime= schedule.getCurrent().getTime()-getCurrentTime();
				long systime = System.currentTimeMillis(); 
				int currentspeed = this.speed;
				try {
					//System.err.println("REMOTE: wait for " + realToAdjusted(waittime, currentspeed));
					this.waitFor(realToAdjusted(waittime, currentspeed));
					this.setCurrentTime(schedule.getCurrent().getTime());
					final ScheduledTraceEvent current = schedule.getCurrent();
					executor.execute(current);
					schedule.next();
				} catch (InterruptedException e) {
					if (this.timeChanged) {
						schedule.goTo(this.currentTime);
						this.timeChanged=false;
					} else if (this.isPaused()){
						this.setCurrentTime(this.currentTime+adjustedToReal(System.currentTimeMillis()-systime, currentspeed));
					} else { // speed was changed
						System.err.println("new speed");
						this.setCurrentTime(this.currentTime+adjustedToReal(System.currentTimeMillis()-systime, currentspeed));
					}	
					if (end) return;
				}
			}
			System.err.println("REMOTE: arrived at the end");
			this.pause();
			try {
				interrupted();
				this.waitFor();
			} catch (InterruptedException e) {
				//get on with it, should now be playing
			}
		}
	}	

	public RemoteControl() {
		schedule = new Schedule();
		replayerList = new HashSet<DiscreteReplayable>();
		continuousList = new HashSet<ContinuousReplayable>();
		rwSWT = new RemoteWidgetSWT(this);
	}

	public void addDiscreteReplayer(DiscreteReplayable replayer) {
		System.err.println("adding replayer " + replayer);
		replayerList.add(replayer);
		addReplayer(replayer);
		updateWidget();
	}


	public void addContinuousReplayer(ContinuousReplayable c) {
		continuousList.add(c);
		addReplayer(c);
		updateWidget();
	}
	
	private void addReplayer(Replayable r) {
		List<Long> events=r.getEvents();
		Iterator<Long> i = events.iterator();
		long last = -MIN_DELTA_BETWEEN_EVENTS;
		while(i.hasNext()) {
			Long k = i.next();
			if (k>=0 && k-last>=MIN_DELTA_BETWEEN_EVENTS) {
				schedule.insertEvent(new TraceEvent(k, r));
				last = k;
			}
		}
	}
	
	public void addEvent(Replayable r, Long time) {
		if (time>=0) {
			schedule.insertEvent(new TraceEvent(time, r));
		}	
		updateWidget();
	}
	
	public void removeReplayer(Replayable r) {
		schedule.removeReplayer(r);
		if (continuousList.contains(r)) continuousList.remove(r);
		if (replayerList.contains(r)) replayerList.remove(r);
		updateWidget();
	}

	synchronized public void pause() {
		this.paused=true;
		//rw.showPaused();
		rwSWT.showPaused();
		this.wakeUp();
		Iterator<ContinuousReplayable> j = continuousList.iterator();
		ContinuousReplayable c;
		while (j.hasNext()) {
			c=j.next();
			c.pause();
		}
	}

	synchronized public void play() {
		this.paused=false;
		//rw.showPlaying();
		rwSWT.showPlaying();
		this.wakeUp();
		Iterator<ContinuousReplayable> j = continuousList.iterator();
		ContinuousReplayable c;
		while (j.hasNext()) {
			c=j.next();
			c.play();
		}
	}

	synchronized public long getCurrentTime() {
		return currentTime;
	}



	/**
	 * Dyke thinks this is only called internally. It means that goTo is not forwarded
	 * except to RemoteWidget
	 * 
	 * @param currentTime
	 */
	synchronized private void setCurrentTime(long currentTime) {
		this.currentTime = currentTime;
	}

	synchronized public int getSpeed() {
		return speed;
	}

	synchronized public void setSpeed(int speed) {
		if (speed<=0) {
			speed=1;
		}
		boolean oldpaused = this.paused;
		this.paused=true;
		wakeUp();
		this.speed = speed;
		this.paused=oldpaused;
		wakeUp();
		//rw.showSpeed(speed);
		rwSWT.showSpeed(speed);
		Iterator<ContinuousReplayable> j = continuousList.iterator();
		ContinuousReplayable c;
		while (j.hasNext()) {
			c=j.next();
			c.setSpeed(speed);
		}
	}

	private void waitFor(long l) throws InterruptedException {
		if (l<0) {
			l=0;
		}
		sleep(l);
	}

	private void waitFor() throws InterruptedException {
		sleep(1000000000);
	}

	private synchronized void wakeUp() {
		this.interrupt();
	}

	private long realToAdjusted(long waittime, int speed) {
//		return (long)((double) waittime/Math.pow(2, speed));
		return waittime / speed;
	}

	private long adjustedToReal(long waittime, int speed) {
//		return (long)((double) waittime*Math.pow(2, speed));
		return waittime * speed;
	}

	public synchronized boolean isPaused() {
		return paused;
	}

	public void mark(long time) {

	}

	public synchronized void end() {
		Iterator<DiscreteReplayable> i = replayerList.iterator();
		DiscreteReplayable r;
		while (i.hasNext()) {
			r=i.next();
			r.end();

		}
		Iterator<ContinuousReplayable> j = continuousList.iterator();
		ContinuousReplayable c;
		while (j.hasNext()) {
			c=j.next();
			c.end();
		}
		end=true;		
		this.wakeUp();
	}

	public synchronized boolean getEnd() {
		return end;
	}

	public List<Long> getEvents() {
		return new LinkedList<Long>();
	}

	public void goTo(SynchronisationEvent event) {
		if (event.getNow() != null) {
			long time = event.getNow();
			if (time <= 0) {
				System.err.println("improbable time");
				return;
			}
			System.err.println("REMOTE: goto "+time);
			this.currentTime = time;
			this.timeChanged = true;
			this.wakeUp();
		}
		if (idsynch) {
			event.setIDsynch(true);
		}
		Iterator<DiscreteReplayable> i = replayerList.iterator();
		DiscreteReplayable r;
		while (i.hasNext() && event.isPropagate()) {
			r=i.next();
			executor.execute(new TraceEvent(event,r));
		}
		Iterator<ContinuousReplayable> j = continuousList.iterator();
		ContinuousReplayable c;
		while (j.hasNext() && event.isPropagate()) {
			c=j.next();
			executor.execute(new TraceEvent(event,c));
		}
		
	}

	public void setWidget(RemoteWidgetSWT rwSWT) {
		rwSWT.setBounds(getStartTime(),getEndTime());
		replayerList.add(rwSWT);
		addReplayer(rwSWT);
	}
	
	private void updateWidget() {
		schedule.removeReplayer(rwSWT);
		setWidget(rwSWT);
		rwSWT.goTo(new SynchronisationEvent(this, currentTime));
	}


	private long getEndTime() {
		if (schedule!=null) {
			return schedule.getLastTime();
		}
		return 0;
	}



	private long getStartTime() {
		if (schedule!=null) {
			return schedule.getFirstTime();
		}
		return 0;
	}
	
	private class GotoExecutor {
	     final Map<Replayable,TraceEvent> current = new HashMap<Replayable,TraceEvent>();
	     final Map<Replayable,TraceEvent> next = new HashMap<Replayable,TraceEvent>();

	     GotoExecutor() {
	     }

	     public synchronized void execute(final TraceEvent te) {
	    	 final Replayable target = te.getTarget();
	    	 if (current.get(target)!=null) {
	    		 next.put(target,te);
	    	 } else {
	    		 current.put(target, te);
	    		 scheduleNext(target);
	    	 }
	     }

	     protected synchronized void scheduleNext(final Replayable target) {
	    	 final TraceEvent te = current.get(target);
	    	 if (te!=null) {
	    		 new Thread(new Runnable() {
		             public void run() {
		                 try {
		                     te.trigger();
		                 } finally {
		                	 current.put(target, next.get(target));
		                	 next.remove(target);
		                     scheduleNext(target);
		                 }
		             }
	    		 }).start(); 
	    	 }

	     }

	 }

	public void setIDSynch(boolean selection) {
		idsynch  = selection;
		
	}
	
}
